// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/output/filter_operations.h"

#include <stddef.h>

#include <cmath>
#include <numeric>

#include "base/trace_event/trace_event_argument.h"
#include "base/values.h"
#include "cc/output/filter_operation.h"
#include "ui/gfx/geometry/rect.h"

namespace cc {

FilterOperations::FilterOperations() { }

FilterOperations::FilterOperations(const FilterOperations& other)
    : operations_(other.operations_)
{
}

FilterOperations::FilterOperations(std::vector<FilterOperation>&& operations)
    : operations_(std::move(operations))
{
}

FilterOperations::~FilterOperations() { }

FilterOperations& FilterOperations::operator=(const FilterOperations& other)
{
    operations_ = other.operations_;
    return *this;
}

FilterOperations& FilterOperations::operator=(FilterOperations&& other)
{
    operations_ = std::move(other.operations_);
    return *this;
}

bool FilterOperations::operator==(const FilterOperations& other) const
{
    if (other.size() != size())
        return false;
    for (size_t i = 0; i < size(); ++i) {
        if (other.at(i) != at(i))
            return false;
    }
    return true;
}

void FilterOperations::Append(const FilterOperation& filter)
{
    operations_.push_back(filter);
}

void FilterOperations::Clear()
{
    operations_.clear();
}

bool FilterOperations::IsEmpty() const
{
    return operations_.empty();
}

static int SpreadForStdDeviation(float std_deviation)
{
    // Corresponds to MapStdDeviation in filter_operation.cc.
    return std_deviation * 3;
}

gfx::Rect FilterOperations::MapRect(const gfx::Rect& rect,
    const SkMatrix& matrix) const
{
    auto accumulate_rect = [matrix](const gfx::Rect& rect,
                               const FilterOperation& op) {
        return op.MapRect(rect, matrix);
    };
    return std::accumulate(operations_.begin(), operations_.end(), rect,
        accumulate_rect);
}

gfx::Rect FilterOperations::MapRectReverse(const gfx::Rect& rect,
    const SkMatrix& matrix) const
{
    auto accumulate_rect = [&matrix](const gfx::Rect& rect,
                               const FilterOperation& op) {
        return op.MapRectReverse(rect, matrix);
    };
    return std::accumulate(operations_.rbegin(), operations_.rend(), rect,
        accumulate_rect);
}

void FilterOperations::GetOutsets(int* top,
    int* right,
    int* bottom,
    int* left) const
{
    *top = *right = *bottom = *left = 0;
    for (size_t i = 0; i < operations_.size(); ++i) {
        const FilterOperation& op = operations_[i];
        // TODO(hendrikw): We should refactor some of this. See crbug.com/523534.
        if (op.type() == FilterOperation::REFERENCE) {
            if (!op.image_filter())
                continue;
            SkIRect src = SkIRect::MakeWH(0, 0);
            SkIRect dst = op.image_filter()->filterBounds(src, SkMatrix::I());
            *top += std::max(0, -dst.top());
            *right += std::max(0, dst.right());
            *bottom += std::max(0, dst.bottom());
            *left += std::max(0, -dst.left());
        } else {
            if (op.type() == FilterOperation::BLUR || op.type() == FilterOperation::DROP_SHADOW) {
                int spread = SpreadForStdDeviation(op.amount());
                if (op.type() == FilterOperation::BLUR) {
                    *top += spread;
                    *right += spread;
                    *bottom += spread;
                    *left += spread;
                } else {
                    *top += std::max(0, spread - op.drop_shadow_offset().y());
                    *right += std::max(0, spread + op.drop_shadow_offset().x());
                    *bottom += std::max(0, spread + op.drop_shadow_offset().y());
                    *left += std::max(0, spread - op.drop_shadow_offset().x());
                }
            }
        }
    }
}

bool FilterOperations::HasFilterThatMovesPixels() const
{
    for (size_t i = 0; i < operations_.size(); ++i) {
        const FilterOperation& op = operations_[i];
        switch (op.type()) {
        case FilterOperation::BLUR:
        case FilterOperation::DROP_SHADOW:
        case FilterOperation::ZOOM:
            return true;
        case FilterOperation::REFERENCE:
            // TODO(hendrikw): SkImageFilter needs a function that tells us if the
            // filter can move pixels. See crbug.com/523538.
            return true;
        case FilterOperation::OPACITY:
        case FilterOperation::COLOR_MATRIX:
        case FilterOperation::GRAYSCALE:
        case FilterOperation::SEPIA:
        case FilterOperation::SATURATE:
        case FilterOperation::HUE_ROTATE:
        case FilterOperation::INVERT:
        case FilterOperation::BRIGHTNESS:
        case FilterOperation::CONTRAST:
        case FilterOperation::SATURATING_BRIGHTNESS:
        case FilterOperation::ALPHA_THRESHOLD:
            break;
        }
    }
    return false;
}

bool FilterOperations::HasFilterThatAffectsOpacity() const
{
    for (size_t i = 0; i < operations_.size(); ++i) {
        const FilterOperation& op = operations_[i];
        // TODO(ajuma): Make this smarter for reference filters. Once SkImageFilter
        // can report affectsOpacity(), call that.
        switch (op.type()) {
        case FilterOperation::OPACITY:
        case FilterOperation::BLUR:
        case FilterOperation::DROP_SHADOW:
        case FilterOperation::ZOOM:
        case FilterOperation::REFERENCE:
        case FilterOperation::ALPHA_THRESHOLD:
            return true;
        case FilterOperation::COLOR_MATRIX: {
            const SkScalar* matrix = op.matrix();
            if (matrix[15] || matrix[16] || matrix[17] || matrix[18] != 1 || matrix[19])
                return true;
            break;
        }
        case FilterOperation::GRAYSCALE:
        case FilterOperation::SEPIA:
        case FilterOperation::SATURATE:
        case FilterOperation::HUE_ROTATE:
        case FilterOperation::INVERT:
        case FilterOperation::BRIGHTNESS:
        case FilterOperation::CONTRAST:
        case FilterOperation::SATURATING_BRIGHTNESS:
            break;
        }
    }
    return false;
}

bool FilterOperations::HasReferenceFilter() const
{
    for (size_t i = 0; i < operations_.size(); ++i) {
        if (operations_[i].type() == FilterOperation::REFERENCE)
            return true;
    }
    return false;
}

FilterOperations FilterOperations::Blend(const FilterOperations& from,
    double progress) const
{
    if (HasReferenceFilter() || from.HasReferenceFilter())
        return *this;

    bool from_is_longer = from.size() > size();

    size_t shorter_size, longer_size;
    if (size() == from.size()) {
        shorter_size = longer_size = size();
    } else if (from_is_longer) {
        longer_size = from.size();
        shorter_size = size();
    } else {
        longer_size = size();
        shorter_size = from.size();
    }

    for (size_t i = 0; i < shorter_size; i++) {
        if (from.at(i).type() != at(i).type())
            return *this;
    }

    FilterOperations blended_filters;
    for (size_t i = 0; i < shorter_size; i++) {
        blended_filters.Append(
            FilterOperation::Blend(&from.at(i), &at(i), progress));
    }

    if (from_is_longer) {
        for (size_t i = shorter_size; i < longer_size; i++) {
            blended_filters.Append(
                FilterOperation::Blend(&from.at(i), NULL, progress));
        }
    } else {
        for (size_t i = shorter_size; i < longer_size; i++)
            blended_filters.Append(FilterOperation::Blend(NULL, &at(i), progress));
    }

    return blended_filters;
}

void FilterOperations::AsValueInto(
    base::trace_event::TracedValue* value) const
{
    for (size_t i = 0; i < operations_.size(); ++i) {
        value->BeginDictionary();
        operations_[i].AsValueInto(value);
        value->EndDictionary();
    }
}

std::string FilterOperations::ToString() const
{
    base::trace_event::TracedValue value;
    value.BeginArray("FilterOperations");
    AsValueInto(&value);
    value.EndArray();
    return value.ToString();
}

} // namespace cc
